/*
 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */
package javax.swing;


import java.awt.*;
import java.awt.event.*;
import java.awt.image.VolatileImage;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.applet.*;

import sun.awt.AWTAccessor;
import sun.awt.AppContext;
import sun.awt.DisplayChangedListener;
import sun.awt.SunToolkit;
import sun.java2d.SunGraphicsEnvironment;
import sun.misc.JavaSecurityAccess;
import sun.misc.SharedSecrets;
import sun.security.action.GetPropertyAction;

import com.sun.java.swing.SwingUtilities3;
import sun.swing.SwingAccessor;
import sun.swing.SwingUtilities2.RepaintListener;

/**
 * This class manages repaint requests, allowing the number
 * of repaints to be minimized, for example by collapsing multiple
 * requests into a single repaint for members of a component tree.
 * <p>
 * As of 1.6 <code>RepaintManager</code> handles repaint requests
 * for Swing's top level components (<code>JApplet</code>,
 * <code>JWindow</code>, <code>JFrame</code> and <code>JDialog</code>).
 * Any calls to <code>repaint</code> on one of these will call into the
 * appropriate <code>addDirtyRegion</code> method.
 *
 * @author Arnaud Weber
 */
public class RepaintManager {

  /**
   * Whether or not the RepaintManager should handle paint requests
   * for top levels.
   */
  static final boolean HANDLE_TOP_LEVEL_PAINT;

  private static final short BUFFER_STRATEGY_NOT_SPECIFIED = 0;
  private static final short BUFFER_STRATEGY_SPECIFIED_ON = 1;
  private static final short BUFFER_STRATEGY_SPECIFIED_OFF = 2;

  private static final short BUFFER_STRATEGY_TYPE;

  /**
   * Maps from GraphicsConfiguration to VolatileImage.
   */
  private Map<GraphicsConfiguration, VolatileImage> volatileMap = new
      HashMap<GraphicsConfiguration, VolatileImage>(1);

  //
  // As of 1.6 Swing handles scheduling of paint events from native code.
  // That is, SwingPaintEventDispatcher is invoked on the toolkit thread,
  // which in turn invokes nativeAddDirtyRegion.  Because this is invoked
  // from the native thread we can not invoke any public methods and so
  // we introduce these added maps.  So, any time nativeAddDirtyRegion is
  // invoked the region is added to hwDirtyComponents and a work request
  // is scheduled.  When the work request is processed all entries in
  // this map are pushed to the real map (dirtyComponents) and then
  // painted with the rest of the components.
  //
  private Map<Container, Rectangle> hwDirtyComponents;

  private Map<Component, Rectangle> dirtyComponents;
  private Map<Component, Rectangle> tmpDirtyComponents;
  private java.util.List<Component> invalidComponents;

  // List of Runnables that need to be processed before painting from AWT.
  private java.util.List<Runnable> runnableList;

  boolean doubleBufferingEnabled = true;

  private Dimension doubleBufferMaxSize;

  // Support for both the standard and volatile offscreen buffers exists to
  // provide backwards compatibility for the [rare] programs which may be
  // calling getOffScreenBuffer() and not expecting to get a VolatileImage.
  // Swing internally is migrating to use *only* the volatile image buffer.

  // Support for standard offscreen buffer
  //
  DoubleBufferInfo standardDoubleBuffer;

  /**
   * Object responsible for hanlding core paint functionality.
   */
  private PaintManager paintManager;

  private static final Object repaintManagerKey = RepaintManager.class;

  // Whether or not a VolatileImage should be used for double-buffered painting
  static boolean volatileImageBufferEnabled = true;
  /**
   * Type of VolatileImage which should be used for double-buffered
   * painting.
   */
  private static final int volatileBufferType;
  /**
   * Value of the system property awt.nativeDoubleBuffering.
   */
  private static boolean nativeDoubleBuffering;

  // The maximum number of times Swing will attempt to use the VolatileImage
  // buffer during a paint operation.
  private static final int VOLATILE_LOOP_MAX = 2;

  /**
   * Number of <code>beginPaint</code> that have been invoked.
   */
  private int paintDepth = 0;

  /**
   * Type of buffer strategy to use.  Will be one of the BUFFER_STRATEGY_
   * constants.
   */
  private short bufferStrategyType;

  //
  // BufferStrategyPaintManager has the unique characteristic that it
  // must deal with the buffer being lost while painting to it.  For
  // example, if we paint a component and show it and the buffer has
  // become lost we must repaint the whole window.  To deal with that
  // the PaintManager calls into repaintRoot, and if we're still in
  // the process of painting the repaintRoot field is set to the JRootPane
  // and after the current JComponent.paintImmediately call finishes
  // paintImmediately will be invoked on the repaintRoot.  In this
  // way we don't try to show garbage to the screen.
  //
  /**
   * True if we're in the process of painting the dirty regions.  This is
   * set to true in <code>paintDirtyRegions</code>.
   */
  private boolean painting;
  /**
   * If the PaintManager calls into repaintRoot during painting this field
   * will be set to the root.
   */
  private JComponent repaintRoot;

  /**
   * The Thread that has initiated painting.  If null it
   * indicates painting is not currently in progress.
   */
  private Thread paintThread;

  /**
   * Runnable used to process all repaint/revalidate requests.
   */
  private final ProcessingRunnable processingRunnable;

  private static final JavaSecurityAccess javaSecurityAccess =
      SharedSecrets.getJavaSecurityAccess();

  /**
   * Listener installed to detect display changes. When display changes,
   * schedules a callback to notify all RepaintManagers of the display
   * changes.
   */
  private static final DisplayChangedListener displayChangedHandler =
      new DisplayChangedHandler();

  static {
    SwingAccessor.setRepaintManagerAccessor(new SwingAccessor.RepaintManagerAccessor() {
      @Override
      public void addRepaintListener(RepaintManager rm, RepaintListener l) {
        rm.addRepaintListener(l);
      }

      @Override
      public void removeRepaintListener(RepaintManager rm, RepaintListener l) {
        rm.removeRepaintListener(l);
      }
    });

    volatileImageBufferEnabled = "true".equals(AccessController.
        doPrivileged(new GetPropertyAction(
            "swing.volatileImageBufferEnabled", "true")));
    boolean headless = GraphicsEnvironment.isHeadless();
    if (volatileImageBufferEnabled && headless) {
      volatileImageBufferEnabled = false;
    }
    nativeDoubleBuffering = "true".equals(AccessController.doPrivileged(
        new GetPropertyAction("awt.nativeDoubleBuffering")));
    String bs = AccessController.doPrivileged(
        new GetPropertyAction("swing.bufferPerWindow"));
    if (headless) {
      BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
    } else if (bs == null) {
      BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_NOT_SPECIFIED;
    } else if ("true".equals(bs)) {
      BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_ON;
    } else {
      BUFFER_STRATEGY_TYPE = BUFFER_STRATEGY_SPECIFIED_OFF;
    }
    HANDLE_TOP_LEVEL_PAINT = "true".equals(AccessController.doPrivileged(
        new GetPropertyAction("swing.handleTopLevelPaint", "true")));
    GraphicsEnvironment ge = GraphicsEnvironment.
        getLocalGraphicsEnvironment();
    if (ge instanceof SunGraphicsEnvironment) {
      ((SunGraphicsEnvironment) ge).addDisplayChangedListener(
          displayChangedHandler);
    }
    Toolkit tk = Toolkit.getDefaultToolkit();
    if ((tk instanceof SunToolkit)
        && ((SunToolkit) tk).isSwingBackbufferTranslucencySupported()) {
      volatileBufferType = Transparency.TRANSLUCENT;
    } else {
      volatileBufferType = Transparency.OPAQUE;
    }
  }

  /**
   * Return the RepaintManager for the calling thread given a Component.
   *
   * @param c a Component -- unused in the default implementation, but could be used by an
   * overridden version to return a different RepaintManager depending on the Component
   * @return the RepaintManager object
   */
  public static RepaintManager currentManager(Component c) {
    // Note: DisplayChangedRunnable passes in null as the component, so if
    // component is ever used to determine the current
    // RepaintManager, DisplayChangedRunnable will need to be modified
    // accordingly.
    return currentManager(AppContext.getAppContext());
  }

  /**
   * Returns the RepaintManager for the specified AppContext.  If
   * a RepaintManager has not been created for the specified
   * AppContext this will return null.
   */
  static RepaintManager currentManager(AppContext appContext) {
    RepaintManager rm = (RepaintManager) appContext.get(repaintManagerKey);
    if (rm == null) {
      rm = new RepaintManager(BUFFER_STRATEGY_TYPE);
      appContext.put(repaintManagerKey, rm);
    }
    return rm;
  }

  /**
   * Return the RepaintManager for the calling thread given a JComponent.
   * <p>
   * Note: This method exists for backward binary compatibility with earlier
   * versions of the Swing library. It simply returns the result returned by
   * {@link #currentManager(Component)}.
   *
   * @param c a JComponent -- unused
   * @return the RepaintManager object
   */
  public static RepaintManager currentManager(JComponent c) {
    return currentManager((Component) c);
  }


  /**
   * Set the RepaintManager that should be used for the calling
   * thread. <b>aRepaintManager</b> will become the current RepaintManager
   * for the calling thread's thread group.
   *
   * @param aRepaintManager the RepaintManager object to use
   */
  public static void setCurrentManager(RepaintManager aRepaintManager) {
    if (aRepaintManager != null) {
      SwingUtilities.appContextPut(repaintManagerKey, aRepaintManager);
    } else {
      SwingUtilities.appContextRemove(repaintManagerKey);
    }
  }

  /**
   * Create a new RepaintManager instance. You rarely call this constructor.
   * directly. To get the default RepaintManager, use
   * RepaintManager.currentManager(JComponent) (normally "this").
   */
  public RepaintManager() {
    // Because we can't know what a subclass is doing with the
    // volatile image we immediately punt in subclasses.  If this
    // poses a problem we'll need a more sophisticated detection algorithm,
    // or API.
    this(BUFFER_STRATEGY_SPECIFIED_OFF);
  }

  private RepaintManager(short bufferStrategyType) {
    // If native doublebuffering is being used, do NOT use
    // Swing doublebuffering.
    doubleBufferingEnabled = !nativeDoubleBuffering;
    synchronized (this) {
      dirtyComponents = new IdentityHashMap<Component, Rectangle>();
      tmpDirtyComponents = new IdentityHashMap<Component, Rectangle>();
      this.bufferStrategyType = bufferStrategyType;
      hwDirtyComponents = new IdentityHashMap<Container, Rectangle>();
    }
    processingRunnable = new ProcessingRunnable();
  }

  private void displayChanged() {
    clearImages();
  }

  /**
   * Mark the component as in need of layout and queue a runnable
   * for the event dispatching thread that will validate the components
   * first isValidateRoot() ancestor.
   *
   * @see JComponent#isValidateRoot
   * @see #removeInvalidComponent
   */
  public synchronized void addInvalidComponent(JComponent invalidComponent) {
    RepaintManager delegate = getDelegate(invalidComponent);
    if (delegate != null) {
      delegate.addInvalidComponent(invalidComponent);
      return;
    }
    Component validateRoot =
        SwingUtilities.getValidateRoot(invalidComponent, true);

    if (validateRoot == null) {
      return;
    }

        /* Lazily create the invalidateComponents vector and add the
         * validateRoot if it's not there already.  If this validateRoot
         * is already in the vector, we're done.
         */
    if (invalidComponents == null) {
      invalidComponents = new ArrayList<Component>();
    } else {
      int n = invalidComponents.size();
      for (int i = 0; i < n; i++) {
        if (validateRoot == invalidComponents.get(i)) {
          return;
        }
      }
    }
    invalidComponents.add(validateRoot);

    // Queue a Runnable to invoke paintDirtyRegions and
    // validateInvalidComponents.
    scheduleProcessingRunnable(SunToolkit.targetToAppContext(invalidComponent));
  }


  /**
   * Remove a component from the list of invalid components.
   *
   * @see #addInvalidComponent
   */
  public synchronized void removeInvalidComponent(JComponent component) {
    RepaintManager delegate = getDelegate(component);
    if (delegate != null) {
      delegate.removeInvalidComponent(component);
      return;
    }
    if (invalidComponents != null) {
      int index = invalidComponents.indexOf(component);
      if (index != -1) {
        invalidComponents.remove(index);
      }
    }
  }


  /**
   * Add a component in the list of components that should be refreshed.
   * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
   * will be unioned with the region that should be redrawn.
   *
   * @see JComponent#repaint
   */
  private void addDirtyRegion0(Container c, int x, int y, int w, int h) {
        /* Special cases we don't have to bother with.
         */
    if ((w <= 0) || (h <= 0) || (c == null)) {
      return;
    }

    if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {
      return;
    }

    if (extendDirtyRegion(c, x, y, w, h)) {
      // Component was already marked as dirty, region has been
      // extended, no need to continue.
      return;
    }

        /* Make sure that c and all it ancestors (up to an Applet or
         * Window) are visible.  This loop has the same effect as
         * checking c.isShowing() (and note that it's still possible
         * that c is completely obscured by an opaque ancestor in
         * the specified rectangle).
         */
    Component root = null;

    // Note: We can't synchronize around this, Frame.getExtendedState
    // is synchronized so that if we were to synchronize around this
    // it could lead to the possibility of getting locks out
    // of order and deadlocking.
    for (Container p = c; p != null; p = p.getParent()) {
      if (!p.isVisible() || (p.getPeer() == null)) {
        return;
      }
      if ((p instanceof Window) || (p instanceof Applet)) {
        // Iconified frames are still visible!
        if (p instanceof Frame &&
            (((Frame) p).getExtendedState() & Frame.ICONIFIED) ==
                Frame.ICONIFIED) {
          return;
        }
        root = p;
        break;
      }
    }

    if (root == null) {
      return;
    }

    synchronized (this) {
      if (extendDirtyRegion(c, x, y, w, h)) {
        // In between last check and this check another thread
        // queued up runnable, can bail here.
        return;
      }
      dirtyComponents.put(c, new Rectangle(x, y, w, h));
    }

    // Queue a Runnable to invoke paintDirtyRegions and
    // validateInvalidComponents.
    scheduleProcessingRunnable(SunToolkit.targetToAppContext(c));
  }

  /**
   * Add a component in the list of components that should be refreshed.
   * If <i>c</i> already has a dirty region, the rectangle <i>(x,y,w,h)</i>
   * will be unioned with the region that should be redrawn.
   *
   * @param c Component to repaint, null results in nothing happening.
   * @param x X coordinate of the region to repaint
   * @param y Y coordinate of the region to repaint
   * @param w Width of the region to repaint
   * @param h Height of the region to repaint
   * @see JComponent#repaint
   */
  public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
    RepaintManager delegate = getDelegate(c);
    if (delegate != null) {
      delegate.addDirtyRegion(c, x, y, w, h);
      return;
    }
    addDirtyRegion0(c, x, y, w, h);
  }

  /**
   * Adds <code>window</code> to the list of <code>Component</code>s that
   * need to be repainted.
   *
   * @param window Window to repaint, null results in nothing happening.
   * @param x X coordinate of the region to repaint
   * @param y Y coordinate of the region to repaint
   * @param w Width of the region to repaint
   * @param h Height of the region to repaint
   * @see JFrame#repaint
   * @see JWindow#repaint
   * @see JDialog#repaint
   * @since 1.6
   */
  public void addDirtyRegion(Window window, int x, int y, int w, int h) {
    addDirtyRegion0(window, x, y, w, h);
  }

  /**
   * Adds <code>applet</code> to the list of <code>Component</code>s that
   * need to be repainted.
   *
   * @param applet Applet to repaint, null results in nothing happening.
   * @param x X coordinate of the region to repaint
   * @param y Y coordinate of the region to repaint
   * @param w Width of the region to repaint
   * @param h Height of the region to repaint
   * @see JApplet#repaint
   * @since 1.6
   */
  public void addDirtyRegion(Applet applet, int x, int y, int w, int h) {
    addDirtyRegion0(applet, x, y, w, h);
  }

  void scheduleHeavyWeightPaints() {
    Map<Container, Rectangle> hws;

    synchronized (this) {
      if (hwDirtyComponents.size() == 0) {
        return;
      }
      hws = hwDirtyComponents;
      hwDirtyComponents = new IdentityHashMap<Container, Rectangle>();
    }
    for (Container hw : hws.keySet()) {
      Rectangle dirty = hws.get(hw);
      if (hw instanceof Window) {
        addDirtyRegion((Window) hw, dirty.x, dirty.y,
            dirty.width, dirty.height);
      } else if (hw instanceof Applet) {
        addDirtyRegion((Applet) hw, dirty.x, dirty.y,
            dirty.width, dirty.height);
      } else { // SwingHeavyWeight
        addDirtyRegion0(hw, dirty.x, dirty.y,
            dirty.width, dirty.height);
      }
    }
  }

  //
  // This is called from the toolkit thread when a native expose is
  // received.
  //
  void nativeAddDirtyRegion(AppContext appContext, Container c,
      int x, int y, int w, int h) {
    if (w > 0 && h > 0) {
      synchronized (this) {
        Rectangle dirty = hwDirtyComponents.get(c);
        if (dirty == null) {
          hwDirtyComponents.put(c, new Rectangle(x, y, w, h));
        } else {
          hwDirtyComponents.put(c, SwingUtilities.computeUnion(
              x, y, w, h, dirty));
        }
      }
      scheduleProcessingRunnable(appContext);
    }
  }

  //
  // This is called from the toolkit thread when awt needs to run a
  // Runnable before we paint.
  //
  void nativeQueueSurfaceDataRunnable(AppContext appContext,
      final Component c, final Runnable r) {
    synchronized (this) {
      if (runnableList == null) {
        runnableList = new LinkedList<Runnable>();
      }
      runnableList.add(new Runnable() {
        public void run() {
          AccessControlContext stack = AccessController.getContext();
          AccessControlContext acc =
              AWTAccessor.getComponentAccessor().getAccessControlContext(c);
          javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction<Void>() {
            public Void run() {
              r.run();
              return null;
            }
          }, stack, acc);
        }
      });
    }
    scheduleProcessingRunnable(appContext);
  }

  /**
   * Extends the dirty region for the specified component to include
   * the new region.
   *
   * @return false if <code>c</code> is not yet marked dirty.
   */
  private synchronized boolean extendDirtyRegion(
      Component c, int x, int y, int w, int h) {
    Rectangle r = dirtyComponents.get(c);
    if (r != null) {
      // A non-null r implies c is already marked as dirty,
      // and that the parent is valid. Therefore we can
      // just union the rect and bail.
      SwingUtilities.computeUnion(x, y, w, h, r);
      return true;
    }
    return false;
  }

  /**
   * Return the current dirty region for a component.
   * Return an empty rectangle if the component is not
   * dirty.
   */
  public Rectangle getDirtyRegion(JComponent aComponent) {
    RepaintManager delegate = getDelegate(aComponent);
    if (delegate != null) {
      return delegate.getDirtyRegion(aComponent);
    }
    Rectangle r;
    synchronized (this) {
      r = dirtyComponents.get(aComponent);
    }
    if (r == null) {
      return new Rectangle(0, 0, 0, 0);
    } else {
      return new Rectangle(r);
    }
  }

  /**
   * Mark a component completely dirty. <b>aComponent</b> will be
   * completely painted during the next paintDirtyRegions() call.
   */
  public void markCompletelyDirty(JComponent aComponent) {
    RepaintManager delegate = getDelegate(aComponent);
    if (delegate != null) {
      delegate.markCompletelyDirty(aComponent);
      return;
    }
    addDirtyRegion(aComponent, 0, 0, Integer.MAX_VALUE, Integer.MAX_VALUE);
  }

  /**
   * Mark a component completely clean. <b>aComponent</b> will not
   * get painted during the next paintDirtyRegions() call.
   */
  public void markCompletelyClean(JComponent aComponent) {
    RepaintManager delegate = getDelegate(aComponent);
    if (delegate != null) {
      delegate.markCompletelyClean(aComponent);
      return;
    }
    synchronized (this) {
      dirtyComponents.remove(aComponent);
    }
  }

  /**
   * Convenience method that returns true if <b>aComponent</b> will be completely
   * painted during the next paintDirtyRegions(). If computing dirty regions is
   * expensive for your component, use this method and avoid computing dirty region
   * if it return true.
   */
  public boolean isCompletelyDirty(JComponent aComponent) {
    RepaintManager delegate = getDelegate(aComponent);
    if (delegate != null) {
      return delegate.isCompletelyDirty(aComponent);
    }
    Rectangle r;

    r = getDirtyRegion(aComponent);
    if (r.width == Integer.MAX_VALUE &&
        r.height == Integer.MAX_VALUE) {
      return true;
    } else {
      return false;
    }
  }


  /**
   * Validate all of the components that have been marked invalid.
   *
   * @see #addInvalidComponent
   */
  public void validateInvalidComponents() {
    final java.util.List<Component> ic;
    synchronized (this) {
      if (invalidComponents == null) {
        return;
      }
      ic = invalidComponents;
      invalidComponents = null;
    }
    int n = ic.size();
    for (int i = 0; i < n; i++) {
      final Component c = ic.get(i);
      AccessControlContext stack = AccessController.getContext();
      AccessControlContext acc =
          AWTAccessor.getComponentAccessor().getAccessControlContext(c);
      javaSecurityAccess.doIntersectionPrivilege(
          new PrivilegedAction<Void>() {
            public Void run() {
              c.validate();
              return null;
            }
          }, stack, acc);
    }
  }


  /**
   * This is invoked to process paint requests.  It's needed
   * for backward compatibility in so far as RepaintManager would previously
   * not see paint requests for top levels, so, we have to make sure
   * a subclass correctly paints any dirty top levels.
   */
  private void prePaintDirtyRegions() {
    Map<Component, Rectangle> dirtyComponents;
    java.util.List<Runnable> runnableList;
    synchronized (this) {
      dirtyComponents = this.dirtyComponents;
      runnableList = this.runnableList;
      this.runnableList = null;
    }
    if (runnableList != null) {
      for (Runnable runnable : runnableList) {
        runnable.run();
      }
    }
    paintDirtyRegions();
    if (dirtyComponents.size() > 0) {
      // This'll only happen if a subclass isn't correctly dealing
      // with toplevels.
      paintDirtyRegions(dirtyComponents);
    }
  }

  private void updateWindows(Map<Component, Rectangle> dirtyComponents) {
    Toolkit toolkit = Toolkit.getDefaultToolkit();
    if (!(toolkit instanceof SunToolkit &&
        ((SunToolkit) toolkit).needUpdateWindow())) {
      return;
    }

    Set<Window> windows = new HashSet<Window>();
    Set<Component> dirtyComps = dirtyComponents.keySet();
    for (Iterator<Component> it = dirtyComps.iterator(); it.hasNext(); ) {
      Component dirty = it.next();
      Window window = dirty instanceof Window ?
          (Window) dirty :
          SwingUtilities.getWindowAncestor(dirty);
      if (window != null &&
          !window.isOpaque()) {
        windows.add(window);
      }
    }

    for (Window window : windows) {
      AWTAccessor.getWindowAccessor().updateWindow(window);
    }
  }

  boolean isPainting() {
    return painting;
  }

  /**
   * Paint all of the components that have been marked dirty.
   *
   * @see #addDirtyRegion
   */
  public void paintDirtyRegions() {
    synchronized (this) {  // swap for thread safety
      Map<Component, Rectangle> tmp = tmpDirtyComponents;
      tmpDirtyComponents = dirtyComponents;
      dirtyComponents = tmp;
      dirtyComponents.clear();
    }
    paintDirtyRegions(tmpDirtyComponents);
  }

  private void paintDirtyRegions(
      final Map<Component, Rectangle> tmpDirtyComponents) {
    if (tmpDirtyComponents.isEmpty()) {
      return;
    }

    final java.util.List<Component> roots =
        new ArrayList<Component>(tmpDirtyComponents.size());
    for (Component dirty : tmpDirtyComponents.keySet()) {
      collectDirtyComponents(tmpDirtyComponents, dirty, roots);
    }

    final AtomicInteger count = new AtomicInteger(roots.size());
    painting = true;
    try {
      for (int j = 0; j < count.get(); j++) {
        final int i = j;
        final Component dirtyComponent = roots.get(j);
        AccessControlContext stack = AccessController.getContext();
        AccessControlContext acc =
            AWTAccessor.getComponentAccessor().getAccessControlContext(dirtyComponent);
        javaSecurityAccess.doIntersectionPrivilege(new PrivilegedAction<Void>() {
          public Void run() {
            Rectangle rect = tmpDirtyComponents.get(dirtyComponent);
            // Sometimes when RepaintManager is changed during the painting
            // we may get null here, see #6995769 for details
            if (rect == null) {
              return null;
            }

            int localBoundsH = dirtyComponent.getHeight();
            int localBoundsW = dirtyComponent.getWidth();
            SwingUtilities.computeIntersection(0,
                0,
                localBoundsW,
                localBoundsH,
                rect);
            if (dirtyComponent instanceof JComponent) {
              ((JComponent) dirtyComponent).paintImmediately(
                  rect.x, rect.y, rect.width, rect.height);
            } else if (dirtyComponent.isShowing()) {
              Graphics g = JComponent.safelyGetGraphics(
                  dirtyComponent, dirtyComponent);
              // If the Graphics goes away, it means someone disposed of
              // the window, don't do anything.
              if (g != null) {
                g.setClip(rect.x, rect.y, rect.width, rect.height);
                try {
                  dirtyComponent.paint(g);
                } finally {
                  g.dispose();
                }
              }
            }
            // If the repaintRoot has been set, service it now and
            // remove any components that are children of repaintRoot.
            if (repaintRoot != null) {
              adjustRoots(repaintRoot, roots, i + 1);
              count.set(roots.size());
              paintManager.isRepaintingRoot = true;
              repaintRoot.paintImmediately(0, 0, repaintRoot.getWidth(),
                  repaintRoot.getHeight());
              paintManager.isRepaintingRoot = false;
              // Only service repaintRoot once.
              repaintRoot = null;
            }

            return null;
          }
        }, stack, acc);
      }
    } finally {
      painting = false;
    }

    updateWindows(tmpDirtyComponents);

    tmpDirtyComponents.clear();
  }


  /**
   * Removes any components from roots that are children of
   * root.
   */
  private void adjustRoots(JComponent root,
      java.util.List<Component> roots, int index) {
    for (int i = roots.size() - 1; i >= index; i--) {
      Component c = roots.get(i);
      for (; ; ) {
        if (c == root || c == null || !(c instanceof JComponent)) {
          break;
        }
        c = c.getParent();
      }
      if (c == root) {
        roots.remove(i);
      }
    }
  }

  Rectangle tmp = new Rectangle();

  void collectDirtyComponents(Map<Component, Rectangle> dirtyComponents,
      Component dirtyComponent,
      java.util.List<Component> roots) {
    int dx, dy, rootDx, rootDy;
    Component component, rootDirtyComponent, parent;
    Rectangle cBounds;

    // Find the highest parent which is dirty.  When we get out of this
    // rootDx and rootDy will contain the translation from the
    // rootDirtyComponent's coordinate system to the coordinates of the
    // original dirty component.  The tmp Rect is also used to compute the
    // visible portion of the dirtyRect.

    component = rootDirtyComponent = dirtyComponent;

    int x = dirtyComponent.getX();
    int y = dirtyComponent.getY();
    int w = dirtyComponent.getWidth();
    int h = dirtyComponent.getHeight();

    dx = rootDx = 0;
    dy = rootDy = 0;
    tmp.setBounds(dirtyComponents.get(dirtyComponent));

    // System.out.println("Collect dirty component for bound " + tmp +
    //                                   "component bounds is " + cBounds);;
    SwingUtilities.computeIntersection(0, 0, w, h, tmp);

    if (tmp.isEmpty()) {
      // System.out.println("Empty 1");
      return;
    }

    for (; ; ) {
      if (!(component instanceof JComponent)) {
        break;
      }

      parent = component.getParent();
      if (parent == null) {
        break;
      }

      component = parent;

      dx += x;
      dy += y;
      tmp.setLocation(tmp.x + x, tmp.y + y);

      x = component.getX();
      y = component.getY();
      w = component.getWidth();
      h = component.getHeight();
      tmp = SwingUtilities.computeIntersection(0, 0, w, h, tmp);

      if (tmp.isEmpty()) {
        // System.out.println("Empty 2");
        return;
      }

      if (dirtyComponents.get(component) != null) {
        rootDirtyComponent = component;
        rootDx = dx;
        rootDy = dy;
      }
    }

    if (dirtyComponent != rootDirtyComponent) {
      Rectangle r;
      tmp.setLocation(tmp.x + rootDx - dx,
          tmp.y + rootDy - dy);
      r = dirtyComponents.get(rootDirtyComponent);
      SwingUtilities.computeUnion(tmp.x, tmp.y, tmp.width, tmp.height, r);
    }

    // If we haven't seen this root before, then we need to add it to the
    // list of root dirty Views.

    if (!roots.contains(rootDirtyComponent)) {
      roots.add(rootDirtyComponent);
    }
  }


  /**
   * Returns a string that displays and identifies this
   * object's properties.
   *
   * @return a String representation of this object
   */
  public synchronized String toString() {
    StringBuffer sb = new StringBuffer();
    if (dirtyComponents != null) {
      sb.append("" + dirtyComponents);
    }
    return sb.toString();
  }


  /**
   * Return the offscreen buffer that should be used as a double buffer with
   * the component <code>c</code>.
   * By default there is a double buffer per RepaintManager.
   * The buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>
   * This happens when the maximum double buffer size as been set for the receiving
   * repaint manager.
   */
  public Image getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
    RepaintManager delegate = getDelegate(c);
    if (delegate != null) {
      return delegate.getOffscreenBuffer(c, proposedWidth, proposedHeight);
    }
    return _getOffscreenBuffer(c, proposedWidth, proposedHeight);
  }

  /**
   * Return a volatile offscreen buffer that should be used as a
   * double buffer with the specified component <code>c</code>.
   * The image returned will be an instance of VolatileImage, or null
   * if a VolatileImage object could not be instantiated.
   * This buffer might be smaller than <code>(proposedWidth,proposedHeight)</code>.
   * This happens when the maximum double buffer size has been set for this
   * repaint manager.
   *
   * @see java.awt.image.VolatileImage
   * @since 1.4
   */
  public Image getVolatileOffscreenBuffer(Component c,
      int proposedWidth, int proposedHeight) {
    RepaintManager delegate = getDelegate(c);
    if (delegate != null) {
      return delegate.getVolatileOffscreenBuffer(c, proposedWidth,
          proposedHeight);
    }

    // If the window is non-opaque, it's double-buffered at peer's level
    Window w = (c instanceof Window) ? (Window) c : SwingUtilities.getWindowAncestor(c);
    if (!w.isOpaque()) {
      Toolkit tk = Toolkit.getDefaultToolkit();
      if ((tk instanceof SunToolkit) && (((SunToolkit) tk).needUpdateWindow())) {
        return null;
      }
    }

    GraphicsConfiguration config = c.getGraphicsConfiguration();
    if (config == null) {
      config = GraphicsEnvironment.getLocalGraphicsEnvironment().
          getDefaultScreenDevice().getDefaultConfiguration();
    }
    Dimension maxSize = getDoubleBufferMaximumSize();
    int width = proposedWidth < 1 ? 1 :
        (proposedWidth > maxSize.width ? maxSize.width : proposedWidth);
    int height = proposedHeight < 1 ? 1 :
        (proposedHeight > maxSize.height ? maxSize.height : proposedHeight);
    VolatileImage image = volatileMap.get(config);
    if (image == null || image.getWidth() < width ||
        image.getHeight() < height) {
      if (image != null) {
        image.flush();
      }
      image = config.createCompatibleVolatileImage(width, height,
          volatileBufferType);
      volatileMap.put(config, image);
    }
    return image;
  }

  private Image _getOffscreenBuffer(Component c, int proposedWidth, int proposedHeight) {
    Dimension maxSize = getDoubleBufferMaximumSize();
    DoubleBufferInfo doubleBuffer;
    int width, height;

    // If the window is non-opaque, it's double-buffered at peer's level
    Window w = (c instanceof Window) ? (Window) c : SwingUtilities.getWindowAncestor(c);
    if (!w.isOpaque()) {
      Toolkit tk = Toolkit.getDefaultToolkit();
      if ((tk instanceof SunToolkit) && (((SunToolkit) tk).needUpdateWindow())) {
        return null;
      }
    }

    if (standardDoubleBuffer == null) {
      standardDoubleBuffer = new DoubleBufferInfo();
    }
    doubleBuffer = standardDoubleBuffer;

    width = proposedWidth < 1 ? 1 :
        (proposedWidth > maxSize.width ? maxSize.width : proposedWidth);
    height = proposedHeight < 1 ? 1 :
        (proposedHeight > maxSize.height ? maxSize.height : proposedHeight);

    if (doubleBuffer.needsReset || (doubleBuffer.image != null &&
        (doubleBuffer.size.width < width ||
            doubleBuffer.size.height < height))) {
      doubleBuffer.needsReset = false;
      if (doubleBuffer.image != null) {
        doubleBuffer.image.flush();
        doubleBuffer.image = null;
      }
      width = Math.max(doubleBuffer.size.width, width);
      height = Math.max(doubleBuffer.size.height, height);
    }

    Image result = doubleBuffer.image;

    if (doubleBuffer.image == null) {
      result = c.createImage(width, height);
      doubleBuffer.size = new Dimension(width, height);
      if (c instanceof JComponent) {
        ((JComponent) c).setCreatedDoubleBuffer(true);
        doubleBuffer.image = result;
      }
      // JComponent will inform us when it is no longer valid
      // (via removeNotify) we have no such hook to other components,
      // therefore we don't keep a ref to the Component
      // (indirectly through the Image) by stashing the image.
    }
    return result;
  }


  /**
   * Set the maximum double buffer size.
   **/
  public void setDoubleBufferMaximumSize(Dimension d) {
    doubleBufferMaxSize = d;
    if (doubleBufferMaxSize == null) {
      clearImages();
    } else {
      clearImages(d.width, d.height);
    }
  }

  private void clearImages() {
    clearImages(0, 0);
  }

  private void clearImages(int width, int height) {
    if (standardDoubleBuffer != null && standardDoubleBuffer.image != null) {
      if (standardDoubleBuffer.image.getWidth(null) > width ||
          standardDoubleBuffer.image.getHeight(null) > height) {
        standardDoubleBuffer.image.flush();
        standardDoubleBuffer.image = null;
      }
    }
    // Clear out the VolatileImages
    Iterator<GraphicsConfiguration> gcs = volatileMap.keySet().iterator();
    while (gcs.hasNext()) {
      GraphicsConfiguration gc = gcs.next();
      VolatileImage image = volatileMap.get(gc);
      if (image.getWidth() > width || image.getHeight() > height) {
        image.flush();
        gcs.remove();
      }
    }
  }

  /**
   * Returns the maximum double buffer size.
   *
   * @return a Dimension object representing the maximum size
   */
  public Dimension getDoubleBufferMaximumSize() {
    if (doubleBufferMaxSize == null) {
      try {
        Rectangle virtualBounds = new Rectangle();
        GraphicsEnvironment ge = GraphicsEnvironment.
            getLocalGraphicsEnvironment();
        for (GraphicsDevice gd : ge.getScreenDevices()) {
          GraphicsConfiguration gc = gd.getDefaultConfiguration();
          virtualBounds = virtualBounds.union(gc.getBounds());
        }
        doubleBufferMaxSize = new Dimension(virtualBounds.width,
            virtualBounds.height);
      } catch (HeadlessException e) {
        doubleBufferMaxSize = new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
      }
    }
    return doubleBufferMaxSize;
  }

  /**
   * Enables or disables double buffering in this RepaintManager.
   * CAUTION: The default value for this property is set for optimal
   * paint performance on the given platform and it is not recommended
   * that programs modify this property directly.
   *
   * @param aFlag true to activate double buffering
   * @see #isDoubleBufferingEnabled
   */
  public void setDoubleBufferingEnabled(boolean aFlag) {
    doubleBufferingEnabled = aFlag;
    PaintManager paintManager = getPaintManager();
    if (!aFlag && paintManager.getClass() != PaintManager.class) {
      setPaintManager(new PaintManager());
    }
  }

  /**
   * Returns true if this RepaintManager is double buffered.
   * The default value for this property may vary from platform
   * to platform.  On platforms where native double buffering
   * is supported in the AWT, the default value will be <code>false</code>
   * to avoid unnecessary buffering in Swing.
   * On platforms where native double buffering is not supported,
   * the default value will be <code>true</code>.
   *
   * @return true if this object is double buffered
   */
  public boolean isDoubleBufferingEnabled() {
    return doubleBufferingEnabled;
  }

  /**
   * This resets the double buffer. Actually, it marks the double buffer
   * as invalid, the double buffer will then be recreated on the next
   * invocation of getOffscreenBuffer.
   */
  void resetDoubleBuffer() {
    if (standardDoubleBuffer != null) {
      standardDoubleBuffer.needsReset = true;
    }
  }

  /**
   * This resets the volatile double buffer.
   */
  void resetVolatileDoubleBuffer(GraphicsConfiguration gc) {
    Image image = volatileMap.remove(gc);
    if (image != null) {
      image.flush();
    }
  }

  /**
   * Returns true if we should use the <code>Image</code> returned
   * from <code>getVolatileOffscreenBuffer</code> to do double buffering.
   */
  boolean useVolatileDoubleBuffer() {
    return volatileImageBufferEnabled;
  }

  /**
   * Returns true if the current thread is the thread painting.  This
   * will return false if no threads are painting.
   */
  private synchronized boolean isPaintingThread() {
    return (Thread.currentThread() == paintThread);
  }
  //
  // Paint methods.  You very, VERY rarely need to invoke these.
  // They are invoked directly from JComponent's painting code and
  // when painting happens outside the normal flow: DefaultDesktopManager
  // and JViewport.  If you end up needing these methods in other places be
  // careful that you don't get stuck in a paint loop.
  //

  /**
   * Paints a region of a component
   *
   * @param paintingComponent Component to paint
   * @param bufferComponent Component to obtain buffer for
   * @param g Graphics to paint to
   * @param x X-coordinate
   * @param y Y-coordinate
   * @param w Width
   * @param h Height
   */
  void paint(JComponent paintingComponent,
      JComponent bufferComponent, Graphics g,
      int x, int y, int w, int h) {
    PaintManager paintManager = getPaintManager();
    if (!isPaintingThread()) {
      // We're painting to two threads at once.  PaintManager deals
      // with this a bit better than BufferStrategyPaintManager, use
      // it to avoid possible exceptions/corruption.
      if (paintManager.getClass() != PaintManager.class) {
        paintManager = new PaintManager();
        paintManager.repaintManager = this;
      }
    }
    if (!paintManager.paint(paintingComponent, bufferComponent, g,
        x, y, w, h)) {
      g.setClip(x, y, w, h);
      paintingComponent.paintToOffscreen(g, x, y, w, h, x + w, y + h);
    }
  }

  /**
   * Does a copy area on the specified region.
   *
   * @param clip Whether or not the copyArea needs to be clipped to the Component's bounds.
   */
  void copyArea(JComponent c, Graphics g, int x, int y, int w, int h,
      int deltaX, int deltaY, boolean clip) {
    getPaintManager().copyArea(c, g, x, y, w, h, deltaX, deltaY, clip);
  }

  private java.util.List<RepaintListener> repaintListeners = new ArrayList<>(1);

  private void addRepaintListener(RepaintListener l) {
    repaintListeners.add(l);
  }

  private void removeRepaintListener(RepaintListener l) {
    repaintListeners.remove(l);
  }

  /**
   * Notify the attached repaint listeners that an area of the {@code c} component
   * has been immediately repainted, that is without scheduling a repaint runnable,
   * due to performing a "blit" (via calling the {@code copyArea} method).
   *
   * @param c the component
   * @param x the x coordinate of the area
   * @param y the y coordinate of the area
   * @param w the width of the area
   * @param h the height of the area
   */
  void notifyRepaintPerformed(JComponent c, int x, int y, int w, int h) {
    for (RepaintListener l : repaintListeners) {
      l.repaintPerformed(c, x, y, w, h);
    }
  }

  /**
   * Invoked prior to any paint/copyArea method calls.  This will
   * be followed by an invocation of <code>endPaint</code>.
   * <b>WARNING</b>: Callers of this method need to wrap the call
   * in a <code>try/finally</code>, otherwise if an exception is thrown
   * during the course of painting the RepaintManager may
   * be left in a state in which the screen is not updated, eg:
   * <pre>
   * repaintManager.beginPaint();
   * try {
   *   repaintManager.paint(...);
   * } finally {
   *   repaintManager.endPaint();
   * }
   * </pre>
   */
  void beginPaint() {
    boolean multiThreadedPaint = false;
    int paintDepth;
    Thread currentThread = Thread.currentThread();
    synchronized (this) {
      paintDepth = this.paintDepth;
      if (paintThread == null || currentThread == paintThread) {
        paintThread = currentThread;
        this.paintDepth++;
      } else {
        multiThreadedPaint = true;
      }
    }
    if (!multiThreadedPaint && paintDepth == 0) {
      getPaintManager().beginPaint();
    }
  }

  /**
   * Invoked after <code>beginPaint</code> has been invoked.
   */
  void endPaint() {
    if (isPaintingThread()) {
      PaintManager paintManager = null;
      synchronized (this) {
        if (--paintDepth == 0) {
          paintManager = getPaintManager();
        }
      }
      if (paintManager != null) {
        paintManager.endPaint();
        synchronized (this) {
          paintThread = null;
        }
      }
    }
  }

  /**
   * If possible this will show a previously rendered portion of
   * a Component.  If successful, this will return true, otherwise false.
   * <p>
   * WARNING: This method is invoked from the native toolkit thread, be
   * very careful as to what methods this invokes!
   */
  boolean show(Container c, int x, int y, int w, int h) {
    return getPaintManager().show(c, x, y, w, h);
  }

  /**
   * Invoked when the doubleBuffered or useTrueDoubleBuffering
   * properties of a JRootPane change.  This may come in on any thread.
   */
  void doubleBufferingChanged(JRootPane rootPane) {
    getPaintManager().doubleBufferingChanged(rootPane);
  }

  /**
   * Sets the <code>PaintManager</code> that is used to handle all
   * double buffered painting.
   *
   * @param paintManager The PaintManager to use.  Passing in null indicates the fallback
   * PaintManager should be used.
   */
  void setPaintManager(PaintManager paintManager) {
    if (paintManager == null) {
      paintManager = new PaintManager();
    }
    PaintManager oldPaintManager;
    synchronized (this) {
      oldPaintManager = this.paintManager;
      this.paintManager = paintManager;
      paintManager.repaintManager = this;
    }
    if (oldPaintManager != null) {
      oldPaintManager.dispose();
    }
  }

  private synchronized PaintManager getPaintManager() {
    if (paintManager == null) {
      PaintManager paintManager = null;
      if (doubleBufferingEnabled && !nativeDoubleBuffering) {
        switch (bufferStrategyType) {
          case BUFFER_STRATEGY_NOT_SPECIFIED:
            Toolkit tk = Toolkit.getDefaultToolkit();
            if (tk instanceof SunToolkit) {
              SunToolkit stk = (SunToolkit) tk;
              if (stk.useBufferPerWindow()) {
                paintManager = new BufferStrategyPaintManager();
              }
            }
            break;
          case BUFFER_STRATEGY_SPECIFIED_ON:
            paintManager = new BufferStrategyPaintManager();
            break;
          default:
            break;
        }
      }
      // null case handled in setPaintManager
      setPaintManager(paintManager);
    }
    return paintManager;
  }

  private void scheduleProcessingRunnable(AppContext context) {
    if (processingRunnable.markPending()) {
      Toolkit tk = Toolkit.getDefaultToolkit();
      if (tk instanceof SunToolkit) {
        SunToolkit.getSystemEventQueueImplPP(context).
            postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
                processingRunnable));
      } else {
        Toolkit.getDefaultToolkit().getSystemEventQueue().
            postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
                processingRunnable));
      }
    }
  }


  /**
   * PaintManager is used to handle all double buffered painting for
   * Swing.  Subclasses should call back into the JComponent method
   * <code>paintToOffscreen</code> to handle the actual painting.
   */
  static class PaintManager {

    /**
     * RepaintManager the PaintManager has been installed on.
     */
    protected RepaintManager repaintManager;
    boolean isRepaintingRoot;

    /**
     * Paints a region of a component
     *
     * @param paintingComponent Component to paint
     * @param bufferComponent Component to obtain buffer for
     * @param g Graphics to paint to
     * @param x X-coordinate
     * @param y Y-coordinate
     * @param w Width
     * @param h Height
     * @return true if painting was successful.
     */
    public boolean paint(JComponent paintingComponent,
        JComponent bufferComponent, Graphics g,
        int x, int y, int w, int h) {
      // First attempt to use VolatileImage buffer for performance.
      // If this fails (which should rarely occur), fallback to a
      // standard Image buffer.
      boolean paintCompleted = false;
      Image offscreen;
      if (repaintManager.useVolatileDoubleBuffer() &&
          (offscreen = getValidImage(repaintManager.
              getVolatileOffscreenBuffer(bufferComponent, w, h))) != null) {
        VolatileImage vImage = (java.awt.image.VolatileImage) offscreen;
        GraphicsConfiguration gc = bufferComponent.
            getGraphicsConfiguration();
        for (int i = 0; !paintCompleted &&
            i < RepaintManager.VOLATILE_LOOP_MAX; i++) {
          if (vImage.validate(gc) ==
              VolatileImage.IMAGE_INCOMPATIBLE) {
            repaintManager.resetVolatileDoubleBuffer(gc);
            offscreen = repaintManager.getVolatileOffscreenBuffer(
                bufferComponent, w, h);
            vImage = (java.awt.image.VolatileImage) offscreen;
          }
          paintDoubleBuffered(paintingComponent, vImage, g, x, y,
              w, h);
          paintCompleted = !vImage.contentsLost();
        }
      }
      // VolatileImage painting loop failed, fallback to regular
      // offscreen buffer
      if (!paintCompleted && (offscreen = getValidImage(
          repaintManager.getOffscreenBuffer(
              bufferComponent, w, h))) != null) {
        paintDoubleBuffered(paintingComponent, offscreen, g, x, y, w,
            h);
        paintCompleted = true;
      }
      return paintCompleted;
    }

    /**
     * Does a copy area on the specified region.
     */
    public void copyArea(JComponent c, Graphics g, int x, int y, int w,
        int h, int deltaX, int deltaY, boolean clip) {
      g.copyArea(x, y, w, h, deltaX, deltaY);
    }

    /**
     * Invoked prior to any calls to paint or copyArea.
     */
    public void beginPaint() {
    }

    /**
     * Invoked to indicate painting has been completed.
     */
    public void endPaint() {
    }

    /**
     * Shows a region of a previously rendered component.  This
     * will return true if successful, false otherwise.  The default
     * implementation returns false.
     */
    public boolean show(Container c, int x, int y, int w, int h) {
      return false;
    }

    /**
     * Invoked when the doubleBuffered or useTrueDoubleBuffering
     * properties of a JRootPane change.  This may come in on any thread.
     */
    public void doubleBufferingChanged(JRootPane rootPane) {
    }

    /**
     * Paints a portion of a component to an offscreen buffer.
     */
    protected void paintDoubleBuffered(JComponent c, Image image,
        Graphics g, int clipX, int clipY,
        int clipW, int clipH) {
      Graphics osg = image.getGraphics();
      int bw = Math.min(clipW, image.getWidth(null));
      int bh = Math.min(clipH, image.getHeight(null));
      int x, y, maxx, maxy;

      try {
        for (x = clipX, maxx = clipX + clipW; x < maxx; x += bw) {
          for (y = clipY, maxy = clipY + clipH; y < maxy; y += bh) {
            osg.translate(-x, -y);
            osg.setClip(x, y, bw, bh);
            if (volatileBufferType != Transparency.OPAQUE
                && osg instanceof Graphics2D) {
              final Graphics2D g2d = (Graphics2D) osg;
              final Color oldBg = g2d.getBackground();
              g2d.setBackground(c.getBackground());
              g2d.clearRect(x, y, bw, bh);
              g2d.setBackground(oldBg);
            }
            c.paintToOffscreen(osg, x, y, bw, bh, maxx, maxy);
            g.setClip(x, y, bw, bh);
            if (volatileBufferType != Transparency.OPAQUE
                && g instanceof Graphics2D) {
              final Graphics2D g2d = (Graphics2D) g;
              final Composite oldComposite = g2d.getComposite();
              g2d.setComposite(AlphaComposite.Src);
              g2d.drawImage(image, x, y, c);
              g2d.setComposite(oldComposite);
            } else {
              g.drawImage(image, x, y, c);
            }
            osg.translate(x, y);
          }
        }
      } finally {
        osg.dispose();
      }
    }

    /**
     * If <code>image</code> is non-null with a positive size it
     * is returned, otherwise null is returned.
     */
    private Image getValidImage(Image image) {
      if (image != null && image.getWidth(null) > 0 &&
          image.getHeight(null) > 0) {
        return image;
      }
      return null;
    }

    /**
     * Schedules a repaint for the specified component.  This differs
     * from <code>root.repaint</code> in that if the RepaintManager is
     * currently processing paint requests it'll process this request
     * with the current set of requests.
     */
    protected void repaintRoot(JComponent root) {
      assert (repaintManager.repaintRoot == null);
      if (repaintManager.painting) {
        repaintManager.repaintRoot = root;
      } else {
        root.repaint();
      }
    }

    /**
     * Returns true if the component being painted is the root component
     * that was previously passed to <code>repaintRoot</code>.
     */
    protected boolean isRepaintingRoot() {
      return isRepaintingRoot;
    }

    /**
     * Cleans up any state.  After invoked the PaintManager will no
     * longer be used anymore.
     */
    protected void dispose() {
    }
  }


  private class DoubleBufferInfo {

    public Image image;
    public Dimension size;
    public boolean needsReset = false;
  }


  /**
   * Listener installed to detect display changes. When display changes,
   * schedules a callback to notify all RepaintManagers of the display
   * changes. Only one DisplayChangedHandler is ever installed. The
   * singleton instance will schedule notification for all AppContexts.
   */
  private static final class DisplayChangedHandler implements
      DisplayChangedListener {

    // Empty non private constructor was added because access to this
    // class shouldn't be generated by the compiler using synthetic
    // accessor method
    DisplayChangedHandler() {
    }

    public void displayChanged() {
      scheduleDisplayChanges();
    }

    public void paletteChanged() {
    }

    private static void scheduleDisplayChanges() {
      // To avoid threading problems, we notify each RepaintManager
      // on the thread it was created on.
      for (AppContext context : AppContext.getAppContexts()) {
        synchronized (context) {
          if (!context.isDisposed()) {
            EventQueue eventQueue = (EventQueue) context.get(
                AppContext.EVENT_QUEUE_KEY);
            if (eventQueue != null) {
              eventQueue.postEvent(new InvocationEvent(
                  Toolkit.getDefaultToolkit(),
                  new DisplayChangedRunnable()));
            }
          }
        }
      }
    }
  }


  private static final class DisplayChangedRunnable implements Runnable {

    public void run() {
      RepaintManager.currentManager((JComponent) null).displayChanged();
    }
  }


  /**
   * Runnable used to process all repaint/revalidate requests.
   */
  private final class ProcessingRunnable implements Runnable {

    // If true, we're wainting on the EventQueue.
    private boolean pending;

    /**
     * Marks this processing runnable as pending. If this was not
     * already marked as pending, true is returned.
     */
    public synchronized boolean markPending() {
      if (!pending) {
        pending = true;
        return true;
      }
      return false;
    }

    public void run() {
      synchronized (this) {
        pending = false;
      }
      // First pass, flush any heavy paint events into real paint
      // events.  If there are pending heavy weight requests this will
      // result in q'ing this request up one more time.  As
      // long as no other requests come in between now and the time
      // the second one is processed nothing will happen.  This is not
      // ideal, but the logic needed to suppress the second request is
      // more headache than it's worth.
      scheduleHeavyWeightPaints();
      // Do the actual validation and painting.
      validateInvalidComponents();
      prePaintDirtyRegions();
    }
  }

  private RepaintManager getDelegate(Component c) {
    RepaintManager delegate = SwingUtilities3.getDelegateRepaintManager(c);
    if (this == delegate) {
      delegate = null;
    }
    return delegate;
  }
}
